<?php

class ModelException extends Exception {}

/**
 * Description of Model
 *
 * @author michamaciejewski
 */
class Model {
  /**
   *
   * @var boolean
   */
  private $_changed = true;

  /**
   *
   * @var boolean
   */
  private $_new =  true;
  /**
   *
   * @var string
   */
  public static $tableName = '';

  /**
   *
   * @var array
   */
  private $_changedObjects = array();
  
  /**
   *
   * @var string
   */
  public static $primary = null;
  
  /**
   *
   * @var boolean
   */
  public static $autoIncrement = false;

  /**
   *
   * @var array 
   */
  public static $fieldsMapping = array();

  /**
   *
   * @var array
   */
  private $_fields = array();

  /**
   *
   * @var array
   */
  public static $hasOne = array();

  /**
   *
   * @var array
   */
  public static $hasMany = array();


  /**
   *
   * @var PDOStatement
   */
  private static $stmtSave = false;

  /**
   *
   * @var PDOStatement
   */
  private static $stmtDelete = false;

  /**
   *
   * @return array
   */
  public function getFields() {
    return $this->_fields;
  }

  /**
   *
   * @return boolean
   */
  public function getChanged() {
    return $this->_changed;
  }

  /**
   *
   * @param boolean $changed
   */
  public function setChanged($changed) {
    $this->_changed = $changed;
  }

  /**
   *
   * @param string $fieldName
   * @return mixed
   */
  public function __get($fieldName) {
    PicoORM::instance()->begin();
    $self = get_class($this);

    $hasOneArray = PicoORM::getStatic($self, 'hasOne');
    $hasManyArray = PicoORM::getStatic($self, 'hasMany');

    if (!key_exists($fieldName, $this->_fields)) {

      if (key_exists($fieldName, $hasOneArray)) {

        $class = $hasOneArray[$fieldName]['model'];
        $object = Query::on($class)
          ->where(PicoORM::getStatic($class, 'primary'))->isEqual($this->_fields[$hasOneArray[$fieldName]['foreign']])
          ->findOne();

        if ($object) {
          $this->_fields[$fieldName] = $object;
        }

      } else if (key_exists($fieldName, $hasManyArray)) {
        $class = $hasManyArray[$fieldName]['model'];
        $objects = Query::on($class)
          ->where($hasManyArray[$fieldName]['foreign'])->isEqual($this->_fields[PicoORM::getStatic($self,'primary')])
          ->find();

        if ($objects) {
          $this->_fields[$fieldName] = $objects;
        }
      } else {
        PicoORM::instance()->rollback();
        throw new ModelException("No such field '$fieldName'!");
      }
    }
    PicoORM::instance()->commit();

    return isset ($this->_fields[$fieldName]) ? $this->_fields[$fieldName] : null ;
  }

  /**
   *
   * @param string $fieldName
   * @param mixed $value
   */
  public function __set($fieldName, $value) {
    $self = get_class($this);
    $found = false;
    $relationalField = false;

    $hasOneArray = PicoORM::getStatic($self, 'hasOne');
    $hasManyArray = PicoORM::getStatic($self, 'hasMany');
    $fieldsMapping = PicoORM::getStatic($self, 'fieldsMapping');

    if (key_exists($fieldName, $hasOneArray)) {
      if ($value instanceof Model || $value == null) {
        $relationalField = true;
        if ($value) {
          $foreignFields = $value->getFields();
          $class = get_class($value);
          $foreignPrimaryField = PicoORM::getStatic($class, 'primary');
          $this->_fields[$hasOneArray[$fieldName]['foreign']] = $foreignFields[$foreignPrimaryField];

        } else {
          $this->_fields[$hasOneArray[$fieldName]['foreign']] = null;
        }

      } else {
        throw new ModelException('Field type is a Model class.');
      }

    } else if (key_exists($fieldName, $hasManyArray)) {
      $relationalField = true;
      $class = $hasManyArray[$fieldName]['model'];
      $foreignKey = $hasManyArray[$fieldName]['foreign'];

      if (is_array($value) || $value == null) {
        if (is_array($this->_fields[$fieldName])) {

          foreach ($this->_fields[$fieldName] as $object) {
            if (! $object instanceof $class) {
              throw new ModelException('Wrong object type!');
            } else {
              $object->$foreignKey = null;
              $this->_changedObjects []= $object;
            }
          }

          unset ($this->_fields[$fieldName]);
        }

        if (is_array($value) && !empty ($value)) {
          $arr = array();

          foreach ($value as $object) {
            if (! $object instanceof $class) {
              throw new ModelException('Wrong object type!');
            } else {
              $object->$foreignKey = $this->_fields[PicoORM::getStatic($self, 'primary')];
              $arr[] = $object;
            }
          }
        }
      } else {
        throw new ModelException('Array or null expected!');
      }
    } else {
      if (isset ($fieldsMapping[$fieldName])) {
        $fieldName = $fieldsMapping[$fieldName];
      }
    }

    $this->setChanged(true);

    if ($value || !$relationalField) {
      $this->_fields[$fieldName] = $value;
    } else {
      unset($this->_fields[$fieldName]);
    }
  }

  public function save() {
    PicoORM::instance()->save($this);
  }

  public function delete() {
    $self = get_class($this);

    $hasManyArray = PicoORM::getStatic($self, 'hasMany');
    $primary = PicoORM::getStatic($self, 'primary');
    
    foreach($hasManyArray as $key => $value) {
      $this->$key = null;
    }
    PicoORM::instance()->delete($this);
  }

  public function reload() {
    $self = get_class($this);
    $primary = PicoORM::getStatic($self, 'primary');

    if (!$this->isNew() && $this->_fields[$primary]) {
      $object = Query::on($self)
        ->where($primary)->isEqual($this->_fields[$primary])
        ->findOne();

      if ($object) {
        $this->_fields = $object->getFields();
        $this->setChanged(false);
      } else {
        throw new ModelException('Reload failed!');
      }

    } else {
      throw new ModelException('Cannot reload class without valid primary key value!');
    }
  }

  /**
   *
   * @return boolean
   */
  public function isNew() {
    return $this->_new;
  }

  /**
   *
   * @param boolean $new
   */
  public function setNew($new) {
    $this->_new = $new;
  }
  
  /**
   *
   * @return array
   */
  public function getChangedObjects() {
    return $this->_changedObjects;
  }

  public function clearChangedObjects() {
    $this->_changedObjects = array();
  }

}
?>